package priv.winnine.interceptor;

import cn.hutool.core.date.DateUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.http.HttpHeaders;
import org.springframework.http.InvalidMediaTypeException;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import priv.winnine.entity.bo.*;
import priv.winnine.entity.enums.ResultEnum;
import priv.winnine.exception.ResultException;
import priv.winnine.service.AuthService;
import priv.winnine.util.AuthUtil;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.util.Map;

@Slf4j
public class AuthInterceptor implements HandlerInterceptor {

    private AuthService authService;
    private static final MediaType APPLICATION_XML_UTF8 = MediaType.valueOf("application/xml;charset=UTF-8");
    private static final String DATE_FORMAT = "yyyyMMddHHmmss";
    private static final long authTimestampLowerLimitMillis = 300_000;
    private static final long authTimestampUpperLimitMillis = 300_000;

    public AuthInterceptor(AuthService authService) {
        this.authService = authService;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        return preHandleRequest(request);
    }

    private boolean preHandleRequest(HttpServletRequest request) {
        RequestInfo requestInfo = getRequestInfoFromRequest(request);
        DeveloperInfo developerInfo = authService.getDeveloperInfoBySid(requestInfo.getSid());
        // 对请求内容做初步的校验，并存入Session中
        request.setAttribute(Constant.HTTP_REQUEST_STRING, getBaseRequestJsonString(request, requestInfo, developerInfo));
        // 验证签名是否正确，是否可以访问目标接口路径
        String servletPath = request.getServletPath();
        if (StringUtils.isEmpty(servletPath)) {
            servletPath = request.getRequestURI();
        }
        authService.checkSignatureAndPath(servletPath, requestInfo, developerInfo);
        // 查询开发者相关信息，并存入Session中
        request.setAttribute(Constant.DEVELOPER_INFO, developerInfo);
        return true;
    }

    private RequestInfo getRequestInfoFromRequest(HttpServletRequest request) {
        String acceptValue = getAcceptValueFromRequest(request);
        String contentTypeValue = getContentTypeValueFromRequest(request);
        String authorization = getAuthorizationFromRequest(request);
        String signature = getSignatureFromRequest(request);
        String baseRequestString = getBaseRequestStringFromRequest(request);
        RequestInfo requestInfo = RequestInfo.builder()
                .acceptValue(acceptValue)
                .contentTypeValue(contentTypeValue)
                .authorization(authorization)
                .signature(signature)
                .baseRequestString(baseRequestString)
                .build();
        doRequestInfoCheck(request, requestInfo);
        return requestInfo;
    }

    private void doRequestInfoCheck(HttpServletRequest request, RequestInfo requestInfo) {
        try {
            requestInfo.setAccept(matchMediaType(requestInfo.getAcceptValue()));
            requestInfo.setContentType(matchMediaType(requestInfo.getContentTypeValue()));
            String authorization = requestInfo.getAuthorization();
            doAuthorizationCheck(authorization);
            requestInfo.setSid(getSidFromAuthorization(authorization));
            String timestamp = getTimestampFromAuthorization(authorization);
            doTimestampCheck(timestamp);
            requestInfo.setTimestamp(timestamp);
            doSignatureCheck(requestInfo.getSignature());
        } catch (Exception e) {
            throw e;
        } finally {
            request.setAttribute(Constant.HTTP_REQUEST_INFO, requestInfo);
        }
    }

    private void doSignatureCheck(String signature) {
        if (StringUtils.isEmpty(signature)) {
            throw new ResultException(ResultEnum.PROPERTY_ERROR, ErrorProperties.builder()
                    .property("signature")
                    .message(InvalidParamDesc.NOT_NULL)
                    .build());
        }
    }

    private void doTimestampCheck(String timestamp) {
        long authTimestamp;
        try {
            authTimestamp = DateUtil.parse(timestamp, DATE_FORMAT).getTime();
        } catch (Exception e) {
            log.error("timestamp wrong format, {}", timestamp);
            throw new ResultException(ResultEnum.PROPERTY_ERROR, ErrorProperties.builder()
                    .property("timestamp")
                    .message(InvalidParamDesc.FORMAT_ERROR)
                    .build());
        }
        long current = System.currentTimeMillis();
        if (authTimestamp < (current - authTimestampLowerLimitMillis)
                || authTimestamp > (current + authTimestampUpperLimitMillis)) {
            log.error("当前时间：" + current + ";   请求头时间：" + authTimestamp);
            throw new ResultException(ResultEnum.TIMESTAMP_NOT_IN_RANGE);
        }

    }

    private String getAcceptValueFromRequest(HttpServletRequest request) {
        return request.getHeader(HttpHeaders.ACCEPT);
    }

    private String getContentTypeValueFromRequest(HttpServletRequest request) {
        return request.getHeader(HttpHeaders.CONTENT_TYPE);
    }

    private MediaType matchMediaType(String mediaTypeValue) {
        if (StringUtils.isEmpty(mediaTypeValue)) {
            throw new ResultException(ResultEnum.NOT_SUPPORT_MEDIA_TYPE);
        }
        MediaType mediaType;
        try {
            mediaType = MediaType.parseMediaType(mediaTypeValue.toLowerCase());
        } catch (InvalidMediaTypeException e) {
            throw new ResultException(ResultEnum.NOT_SUPPORT_MEDIA_TYPE);
        }
        if (MediaType.APPLICATION_JSON_UTF8.equals(mediaType)
                || MediaType.APPLICATION_JSON.equals(mediaType)
                || APPLICATION_XML_UTF8.equals(mediaType)
                || MediaType.APPLICATION_XML.equals(mediaType)) {
            return mediaType;
        }
        throw new ResultException(ResultEnum.NOT_SUPPORT_MEDIA_TYPE);
    }

    private String getAuthorizationFromRequest(HttpServletRequest request) {
        return request.getHeader(HttpHeaders.AUTHORIZATION);
    }

    private void doAuthorizationCheck(String authorization) {
        String sourceAuth;
        try {
            sourceAuth = new String(Base64.decodeBase64(authorization));
        } catch (Exception e) {
            throw new ResultException(ResultEnum.PROPERTY_ERROR, ErrorProperties.builder()
                    .property("authorization")
                    .message(InvalidParamDesc.FORMAT_ERROR)
                    .build());
        }
        String[] sidTimestampArray = sourceAuth.split(Constant.COLON);
        if (sidTimestampArray.length != 2 || StringUtils.isEmpty(sidTimestampArray[0]) || StringUtils.isEmpty(sidTimestampArray[1])) {
            throw new ResultException(ResultEnum.PROPERTY_ERROR, ErrorProperties.builder()
                    .property("authorization")
                    .message(InvalidParamDesc.FORMAT_ERROR)
                    .build());
        }
    }

    private String getSignatureFromRequest(HttpServletRequest request) {
        return request.getParameter(Constant.SIG);
    }

    private String getSidFromAuthorization(String authorization) {
        String[] sidTimestampArray = new String(Base64.decodeBase64(authorization)).split(Constant.COLON);
        return sidTimestampArray[0];
    }

    private String getTimestampFromAuthorization(String authorization) {
        String[] sidTimestampArray = new String(Base64.decodeBase64(authorization)).split(Constant.COLON);
        return sidTimestampArray[1];
    }

    private String getBaseRequestStringFromRequest(HttpServletRequest request) {
        try (BufferedReader br = request.getReader();) {
            StringBuilder sb = new StringBuilder();
            String str;
            while ((str = br.readLine()) != null) {
                sb.append(str);
            }
            return sb.toString();
        } catch (Exception e) {
            throw new ResultException(ResultEnum.REQUEST_BODY_ERROR);
        }
    }

    private String getBaseRequestJsonString(HttpServletRequest request, RequestInfo requestInfo, DeveloperInfo developerInfo) {
        String baseRequestString = requestInfo.getBaseRequestString();
        MediaType contentType = requestInfo.getContentType();
        Map map;
        try {
            if (MediaType.APPLICATION_JSON_UTF8.equals(contentType) || MediaType.APPLICATION_JSON.equals(contentType)) {
                // 处理Json格式的数据
                ObjectMapper objectMapper = new ObjectMapper();
                map = objectMapper.readValue(baseRequestString, Map.class);
            } else {
                // 处理Xml格式的数据
                XmlMapper xmlMapper = new XmlMapper();
                map = xmlMapper.readValue(baseRequestString, Map.class);
            }

        } catch (Exception e) {
            throw new ResultException(ResultEnum.REQUEST_BODY_ERROR);
        }
        doFormatCheck(request, developerInfo, map);
        try {
            return new ObjectMapper().writeValueAsString(map);
        } catch (Exception e) {
            throw new ResultException(ResultEnum.REQUEST_BODY_ERROR);
        }
    }

    private void doFormatCheck(HttpServletRequest request, DeveloperInfo developerInfo, Map map) {
        Object data = map.get("data");
        if (data instanceof String && data.toString().length() > 255) {
            throw new ResultException(ResultEnum.PROPERTY_ERROR, ErrorProperties.builder()
                    .property("data")
                    .message(InvalidParamDesc.MAX_LENGTH_255)
                    .build());
        } else if (!(data instanceof String) && data != null) {
            throw new ResultException(ResultEnum.PROPERTY_ERROR, ErrorProperties.builder()
                    .property("data")
                    .message(InvalidParamDesc.FORMAT_ERROR)
                    .build());
        }
        request.setAttribute(Constant.HTTP_HEADER_DATA, data);
        Object info = map.get("info");
        if (info == null) {
            throw new ResultException(ResultEnum.PROPERTY_ERROR, ErrorProperties.builder()
                    .property("info")
                    .message(InvalidParamDesc.NOT_NULL)
                    .build());
        } else if (!(info instanceof Map)) {
            throw new ResultException(ResultEnum.PROPERTY_ERROR, ErrorProperties.builder()
                .property("info")
                .message(InvalidParamDesc.FORMAT_ERROR)
                .build());
        }
        Object subject = map.get("subject");
        if (subject == null) {
            throw new ResultException(ResultEnum.PROPERTY_ERROR, ErrorProperties.builder()
                    .property("subject")
                    .message(InvalidParamDesc.NOT_NULL)
                    .build());
        } else if (!(subject instanceof Map)) {
            throw new ResultException(ResultEnum.PROPERTY_ERROR, ErrorProperties.builder()
                    .property("subject")
                    .message(InvalidParamDesc.FORMAT_ERROR)
                    .build());
        }
        try {
            String appID = (String) ((Map) info).get("appID");
            if (StringUtils.isEmpty(appID)) {
                throw new ResultException(ResultEnum.PROPERTY_ERROR, ErrorProperties.builder()
                        .property("appID")
                        .message(InvalidParamDesc.NOT_NULL)
                        .build());
            }
            if (!authService.checkAppID(developerInfo.getSid(), appID)) {
                throw new ResultException(ResultEnum.APP_ID_NOT_AVAILABLE);
            }
            developerInfo.setAppId(appID);
        } catch (ResultException re) {
            throw re;
        } catch (Exception e) {
            throw new ResultException(ResultEnum.FAIL);
        }
    }

}
